﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace Songhay.OpenXml.Extensions
{
    using Songhay.OpenXml.Models;

    /// <summary>
    /// Extensions for <see cref="System.Xml.Linq.XElement"/>.
    /// </summary>
    public static class XElementExtensions
    {
        /// <summary>
        /// Trims the descendants of the specified element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="trimName">Name of the trim.</param>
        /// <remarks>
        /// This member is from PowerTools for Open XML (http://powertools.codeplex.com/)
        /// </remarks>
        public static IEnumerable<XElement> DescendantsTrimmed(this XElement element,
            XName trimName)
        {
            return DescendantsTrimmed(element, e => e.Name == trimName);
        }

        /// <summary>
        /// Trims the descendants of the specified element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="predicate">The predicate.</param>
        /// <remarks>
        /// This member is from PowerTools for Open XML (http://powertools.codeplex.com/)
        /// </remarks>
        public static IEnumerable<XElement> DescendantsTrimmed(this XElement element,
            Func<XElement, bool> predicate)
        {
            Stack<IEnumerator<XElement>> iteratorStack = new Stack<IEnumerator<XElement>>();
            iteratorStack.Push(element.Elements().GetEnumerator());
            while(iteratorStack.Count > 0)
            {
                while(iteratorStack.Peek().MoveNext())
                {
                    XElement currentXElement = iteratorStack.Peek().Current;
                    if(predicate(currentXElement))
                    {
                        yield return currentXElement;
                        continue;
                    }
                    yield return currentXElement;
                    iteratorStack.Push(currentXElement.Elements().GetEnumerator());
                }
                iteratorStack.Pop();
            }
        }

        public static IEnumerable<XElement> ElementsBeforeSelfReverseDocumentOrder(
            this XElement element)
        {
            if(element.Annotation<ReverseDocumentOrderInfo>() == null)
                InitializeReverseDocumentOrder(element.Parent);
            XElement current = element;
            while(true)
            {
                XElement previousElement = current
                    .Annotation<ReverseDocumentOrderInfo>()
                    .PreviousSibling;
                if(previousElement == null)
                    yield break;
                yield return previousElement;
                current = previousElement;
            }
        }


        /// <summary>
        /// Gets the <c>w:sdt</c> property value.
        /// </summary>
        /// <param name="sdt">The <c>w:sdt</c>.</param>
        /// <param name="propertyName">Name of the property.</param>
        public static string GetSdtPropertyValue(this XElement sdt, XName propertyName)
        {
            var sdtPr = sdt.Elements(SonghayW.sdtPr).FirstOrDefault();
            if(sdtPr == null) return null;
            var property = sdtPr.Elements(propertyName).FirstOrDefault();
            if(property == null) return null;
            return (string)property.Attribute(SonghayW.val);
        }

        /// <summary>
        /// Gets the <c>w:sdt</c> element conventional value.
        /// </summary>
        /// <param name="sdt">The <c>w:sdt</c> element.</param>
        public static string GetSdtDisplayValue(this XElement sdt)
        {
            var sdtContent = sdt.Element(SonghayW.sdtContent);
            var r = sdtContent.Descendants(SonghayW.r).FirstOrDefault();
            if(r == null) return null;
            var t = r.Element(SonghayW.t);
            return t.Value;
        }

        /// <summary>
        /// Determines whether the <c>w:sdt</c> has the specified <c>w:name</c>.
        /// </summary>
        /// <param name="sdt">The <c>w:sdt</c>.</param>
        /// <param name="tagName"><c>w:name</c> of the tag.</param>
        /// <returns>
        /// 	<c>true</c> if the <c>w:sdt</c> has the specified <c>w:name</c>; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasSdtTagName(this XElement sdt, string tagName)
        {
            return HasSdtPropertyValue(sdt, SonghayW.tag, tagName);
        }

        /// <summary>
        /// Get the content of the children
        /// of the specified element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <remarks>
        /// This member is from PowerTools for Open XML (http://powertools.codeplex.com/)
        /// </remarks>
        public static IEnumerable<XElement> LogicalChildrenContent(this XElement element)
        {
            if(element.Name == SonghayW.document)
                return element.Descendants(SonghayW.body).Take(1);
            if(element.Name == SonghayW.body ||
                element.Name == SonghayW.tc ||
                element.Name == SonghayW.txbxContent)
                return element
                    .DescendantsTrimmed(e =>
                        e.Name == SonghayW.tbl ||
                        e.Name == SonghayW.p)
                    .Where(e =>
                        e.Name == SonghayW.p ||
                        e.Name == SonghayW.tbl);
            if(element.Name == SonghayW.tbl)
                return element
                    .DescendantsTrimmed(SonghayW.tr)
                    .Where(e => e.Name == SonghayW.tr);
            if(element.Name == SonghayW.tr)
                return element
                    .DescendantsTrimmed(SonghayW.tc)
                    .Where(e => e.Name == SonghayW.tc);
            if(element.Name == SonghayW.p)
                return element
                    .DescendantsTrimmed(e => e.Name == SonghayW.r ||
                        e.Name == SonghayW.pict ||
                        e.Name == SonghayW.drawing)
                    .Where(e => e.Name == SonghayW.r ||
                        e.Name == SonghayW.pict ||
                        e.Name == SonghayW.drawing);
            if(element.Name == SonghayW.r)
                return element
                    .DescendantsTrimmed(e => SonghayW.SubRunLevelContent.Contains(e.Name))
                    .Where(e => SonghayW.SubRunLevelContent.Contains(e.Name));
            if(element.Name == SonghayMC.AlternateContent)
                return element
                    .DescendantsTrimmed(e =>
                        e.Name == SonghayW.pict ||
                        e.Name == SonghayW.drawing ||
                        e.Name == SonghayMC.Fallback)
                    .Where(e =>
                        e.Name == SonghayW.pict ||
                        e.Name == SonghayW.drawing);
            if(element.Name == SonghayW.pict || element.Name == SonghayW.drawing)
                return element
                    .DescendantsTrimmed(SonghayW.txbxContent)
                    .Where(e => e.Name == SonghayW.txbxContent);
            return XElement.EmptySequence;
        }

        /// <summary>
        /// Get the content of the children
        /// of the specified element.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <remarks>
        /// This member is from PowerTools for Open XML (http://powertools.codeplex.com/)
        /// </remarks>
        public static IEnumerable<XElement> LogicalChildrenContent(
            this IEnumerable<XElement> source)
        {
            foreach(XElement e1 in source)
                foreach(XElement e2 in e1.LogicalChildrenContent())
                    yield return e2;
        }

        /// <summary>
        /// Get the content of the children
        /// of the specified element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="name">The name.</param>
        /// <remarks>
        /// This member is from PowerTools for Open XML (http://powertools.codeplex.com/)
        /// </remarks>
        public static IEnumerable<XElement> LogicalChildrenContent(
            this XElement element, XName name)
        {
            return element.LogicalChildrenContent().Where(e => e.Name == name);
        }

        /// <summary>
        /// Get the content of the children
        /// of the specified element.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="name">The name.</param>
        /// <remarks>
        /// This member is from PowerTools for Open XML (http://powertools.codeplex.com/)
        /// </remarks>
        public static IEnumerable<XElement> LogicalChildrenContent(
            this IEnumerable<XElement> source, XName name)
        {
            foreach(XElement e1 in source)
                foreach(XElement e2 in e1.LogicalChildrenContent(name))
                    yield return e2;
        }

        static bool HasSdtPropertyValue(XElement sdt, XName propertyName, string propertyValue)
        {
            var sdtPr = sdt.Elements(SonghayW.sdtPr).FirstOrDefault();
            if(sdtPr == null) return false;
            var property = sdtPr.Elements(propertyName).FirstOrDefault();
            if(property == null) return false;
            var test = ((string)property
                .Attributes(SonghayW.val)
                .FirstOrDefault() == propertyValue);
            return test;
        }

        static void InitializeReverseDocumentOrder(XElement element)
        {
            XElement prev = null;
            foreach(XElement e in element.Elements())
            {
                e.AddAnnotation(new ReverseDocumentOrderInfo { PreviousSibling = prev });
                prev = e;
            }
        }
    }
}
